<template>
  <Layout desc="G6实现产业链结构图">
    <div class="border relative mt-5">
      <div id="container" class="relative" style="transform: scale(scaleValue)">
        <div
          class="absolute top-5 right-5 flex items-center gap-2 cursor-pointer text-sm z-20 bg-white p-2 rounded-md shadow"
        >
          <div @click="handleZoom(1)">放大</div>
          <div @click="handleZoom(-1)">缩小</div>
          <div @click="handleZoom(0)">还原</div>
          <div @click="downloadImage">保存图片</div>
          <div @click="handleFullscreen">
            {{ isFullscreen ? '退出全屏' : '全屏查看' }}
          </div>
        </div>
      </div>
    </div>
  </Layout>
</template>

<script setup lang="ts">
import { Graph, NodeData, NodeEvent, NodeOptions } from '@antv/g6'
import data from './data.json'

const getMap = (type: any) => {
  switch (type) {
    case 1:
      return ['#ecf7f1', '#479974', '上游']
    case 2:
      return ['#e7f3ff', '#1586ff', '中游']
    case 3:
      return ['#ece7fb', '#6b35d9', '下游']
    default:
      return ['#1586ff', '#1586ff', '']
  }
}

const circleNode = {
  type: 'circle',
  style: (node: NodeData) => ({
    cursor: 'pointer',
    size: 40,
    iconFontSize: 12,
    iconFill: '#FFF',
    fill: getMap(node.data!.type)[1],
    iconText: getMap(node.data!.type)[2]
  })
} as NodeOptions

const rectNode = {
  type: 'rect',
  style: (node: NodeData) => {
    const isCollapsed = node.data!.collapsed

    return {
      cursor: 'pointer',
      size: [120, 30],
      lineWidth: 1,
      iconFontSize: 12,
      fill: getMap(node.data!.type)[0],
      stroke: getMap(node.data!.type)[1],
      iconFill: node.data!.type === 0 ? '#fff' : '#666',
      iconText: node.data!.name,
      badges: [
        node.data!.nodeType === 'rect' &&
          node.data!.type !== 0 &&
          node.data!.hasChildren && {
            placement: node.data!.type === 1 ? 'left' : 'right',
            fontSize: 12,
            text: isCollapsed ? '+' : '-',
            stroke: '#fff',
            lineWidth: 1,
            cursor: 'pointer'
          }
      ]
    }
  }
} as NodeOptions

const getTextWidth = (text: string): number => {
  const canvas = document.createElement('canvas')
  const context = canvas.getContext('2d')
  if (context) {
    context.font = '12px sans-serif'
    return context.measureText(text).width
  }
  return 0
}

const htmlNode = {
  type: 'html',
  style: (node: NodeData) => {
    const isCollapsed = node.data!.collapsed
    const htmlContent = `
  <div style="border: 1px solid ${
    getMap(node.data!.type)[1]
  }; background-color: ${
      getMap(node.data!.type)[0]
    }; text-align:center;  height:35px; padding: 1px 0;">
    <div style="font-size: 12px; color: ${
      node.data!.type === 0 ? '#fff' : '#666'
    };">
      ${node.data!.name}
    </div>
    <div style="font-size: 10px; color: #666;"><span style="color: #1586ff; ">120</span>家相关企业</div>
  </div>
`
    return {
      cursor: 'pointer',
      size: [Math.max(getTextWidth(node.data!.name as string) + 50, 120), 35],
      innerHTML: htmlContent,
      badges: [
        node.data!.nodeType === 'html' &&
          node.data!.type !== 0 &&
          node.data!.hasChildren && {
            placement: node.data!.type === 1 ? 'left' : 'right',
            offsetX: node.data!.type === 1 ? -4 : 4,
            fontSize: 12,
            text: isCollapsed ? '+' : '-',
            stroke: '#fff',
            lineWidth: 1,
            cursor: 'pointer'
          }
      ]
    }
  }
}

const genEdges = (source: string, target: string) => {
  return {
    id: useId(),
    type: 'polyline',
    source,
    target,
    style: {
      endArrow: !(source === 'root'),
      router: {
        type: 'orth'
      }
    }
  }
}

const nodes = [
  {
    id: 'root',
    type: 'rect',
    data: { nodeType: 'rect', type: 0, name: '根节点' },
    style: { x: 0, y: 0 }
  },
  {
    id: '上游',
    data: { nodeType: 'circle', type: 1 }
  },
  {
    id: '中游',
    data: { nodeType: 'circle', type: 2 }
  },
  {
    id: '下游',
    data: { nodeType: 'circle', type: 3 }
  }
] as any

const edges = [
  genEdges('root', '上游'),
  genEdges('root', '中游'),
  genEdges('root', '下游')
]

const genChildrenById = (id: string, type: number, data: any) => {
  data?.children.forEach((item: any) => {
    nodes.push({
      id: item.id,
      data: {
        nodeType: 'html',
        type: type + 1,
        name: item.name,
        collapsed: false,
        hasChildren: !!item.children?.length
      }
    })
    edges.push(genEdges(id, item.id))

    genChildrenById(item.id, type, item)
  })
}

const genDepts1 = (data: any) => {
  const rootArr = ['上游', '中游', '下游']

  data.forEach((item: any, index: number) => {
    item.forEach((item2: any) => {
      nodes.push({
        id: item2.id,
        data: {
          nodeType: 'html',
          type: index + 1,
          name: item2.name,
          collapsed: false,
          hasChildren: item2.children?.length
        }
      })
      edges.push(genEdges(rootArr[index], item2.id))
      genChildrenById(
        item2.id,
        index,
        data[index].find((item: any) => item.id === item2.id)
      )
    })
  })
}

genDepts1(data)

let graph: Graph

onMounted(() => {
  graph = new Graph({
    container: document.getElementById('container')!,
    width: 1200,
    height: 550,
    autoFit: 'center',
    behaviors: ['drag-canvas', 'zoom-canvas', 'focus-element'],
    plugins: [
      {
        type: 'background',
        key: 'background',
        backgroundColor: '#FFF'
      },
      {
        type: 'fullscreen',
        key: 'fullscreen',
        autoFit: true
      }
    ],
    node: {
      type: (node: NodeData) => (node.data!.nodeType as string) || 'circle',
      style: (node: NodeData) => {
        const nodeTypeMap = {
          circle: circleNode,
          rect: rectNode,
          html: htmlNode
        }

        // @ts-ignore
        const nodeType = nodeTypeMap[node.data!.nodeType] || rectNode
        return nodeType.style(node)
      }
    },

    layout: {
      type: 'mindmap',
      direction: 'H',
      getHeight: () => 30,
      getWidth: () => 120,
      getVGap: () => 16,
      getHGap: () => 32,
      getSide: ({ data }: any) => (data.data.type === 1 ? 'left' : 'right')
    },

    data: {
      nodes,
      edges
    }
  })

  graph.render()

  graph?.on(NodeEvent.CLICK, handleNodeClick)
})

const findParentNodes = (data: any, targetId: any, maxDepth = 3) => {
  const parents: { id: any; name: any }[] = []

  function findParentsRecursive(nodes: any, targetId: any, currentDepth = 0) {
    if (currentDepth >= maxDepth) {
      return false
    }
    for (const node of nodes) {
      if (
        node.children &&
        node.children.some((child: { id: any }) => child.id === targetId)
      ) {
        parents.unshift({ id: node.id, name: node.name })
        return findParentsRecursive(data, node.id, currentDepth + 1)
      }

      if (node.children) {
        const found = findParentsRecursive(
          node.children,
          targetId,
          currentDepth + 1
        )
        if (found) {
          parents.unshift({ id: node.id, name: node.name })
          return true
        }
      }
    }
    return false
  }
  findParentsRecursive(data, targetId)
  return parents
}

const findChildIds = (id: any) => {
  const ids: string[] = []

  function collectChildIds(nodes: any[]) {
    nodes.forEach((node: any) => {
      if (node.id === id && node.children) {
        function collectAllChildren(children: any[]) {
          children.forEach((child: any) => {
            ids.push(child.id)
            if (child.children && child.children.length > 0) {
              collectAllChildren(child.children)
            }
          })
        }
        collectAllChildren(node.children)
      } else if (node.children) {
        collectChildIds(node.children)
      }
    })
  }

  data.forEach((deptData: any) => {
    collectChildIds(deptData)
  })
  return ids
}

const addNodeData = (ids: string[]) => {
  const subNodes = nodes.filter((item: any) => ids.includes(item.id))
  const subEdges = edges.filter((item: any) => ids.includes(item.target))

  const existingNodeIds = new Set(
    graph.getNodeData().map((node: any) => node.id)
  )
  const existingEdgeIds = new Set(
    graph.getEdgeData().map((edge: any) => edge.id)
  )

  const newNodes = subNodes.filter((node: any) => !existingNodeIds.has(node.id))
  const newEdges = subEdges.filter((edge: any) => !existingEdgeIds.has(edge.id))

  if (newNodes.length > 0) {
    graph.addNodeData(newNodes)
  }
  if (newEdges.length > 0) {
    graph.addEdgeData(newEdges)
  }
}

const removeNodeData = (ids: string[]) => {
  const existingNodes = graph.getNodeData()
  const nodesToRemove = ids.filter(id =>
    existingNodes.some((node: any) => node.id === id)
  )

  if (nodesToRemove.length > 0) {
    graph.removeNodeData(nodesToRemove)
  }
}

const handleNodeClick = async (evt: any) => {
  const { target } = evt
  const nodeData = graph!.getNodeData(target.id)

  const {
    id,
    // @ts-ignore
    data: { type, name, nodeType }
  } = nodeData!

  if (id === 'root' || nodeType === 'circle') return

  // 展开收起
  if (evt.path.length > 7) {
    const isCollapsed = nodeData.data?.collapsed
    graph.updateNodeData([
      {
        id,
        data: {
          collapsed: !isCollapsed
        }
      }
    ])

    const subIds = findChildIds(id)

    isCollapsed ? addNodeData(subIds) : removeNodeData(subIds)

    graph.render()

    return
  }

  // 跳转
  const parents = findParentNodes(data[type - 1], id)
  const clickedArr = [...parents, { id, name }]
  console.log(clickedArr)
}

const downloadImage = async () => {
  const dataURL = await graph!.toDataURL()
  const [head, content] = dataURL.split(',')
  const contentType = head.match(/:(.*?);/)![1]

  const bstr = atob(content)
  let length = bstr.length
  const u8arr = new Uint8Array(length)

  while (length--) {
    u8arr[length] = bstr.charCodeAt(length)
  }

  const blob = new Blob([u8arr], { type: contentType })

  const url = URL.createObjectURL(blob)
  const a = document.createElement('a')
  a.href = url
  a.download = 'graph.png'
  a.click()
}

const { isFullscreen } = useFullscreen()

const handleFullscreen = () => {
  const instance = graph!.getPluginInstance('fullscreen')

  // @ts-ignore
  isFullscreen.value ? instance.exit() : instance.request()
  isFullscreen.value = !isFullscreen.value
}

const scaleValue = ref(1)
const handleZoom = (val: number) => {
  if (val === 0) {
    scaleValue.value = 1
    graph!.zoomTo(1)
    return
  }
  scaleValue.value += val * 0.2
  graph!.zoomTo(scaleValue.value)
}
</script>

<style scoped lang="scss"></style>
